﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.VisualStudio.Modeling;
using Microsoft.VisualStudio.Modeling.Diagrams;
using Microsoft.VisualStudio.Modeling.Diagnostics;
using UofCASE.AgileProductLinerDSL;

namespace APLDTests {
    class ModelHelper {

        private static ModelHelper instance = null;

        private Store store = null;

        private IDictionary<String, Object> modelElements;

        private FeatureModel featureModel;

        private Feature feature;
        private Feature feature2;
        private Feature feature3;
        private Feature feature4;

        private FeatureShape featureShape;
        private FeatureShape featureShape2;
        private FeatureShape featureShape3;
        private FeatureShape featureShape4;

        private FeatureModelHasFeatureModelElements rootFeatureLink;

        private FeatureModelElementReferencesSubFeatureModelElements link;
        private FeatureModelElementReferencesSubFeatureModelElements link2;
        private FeatureModelElementReferencesSubFeatureModelElements link3;

        private Test test;
        private Test test2;
        private Test test3;

        private TestShape testShape;
        private TestShape testShape2;
        private TestShape testShape3;

        private TestPort testPort;
        private TestPort testPort2;
        private TestPort testPort3;

        private TestPortShape testPortShape;
        private TestPortShape testPortShape2;
        private TestPortShape testPortShape3;

        private FeaturePort featPort;
        private FeaturePort featPort2;
        private FeaturePort featPort3;
        private FeaturePort featPort4;

        private FeaturePortShape featPortShape;
        private FeaturePortShape featPortShape2;
        private FeaturePortShape featPortShape3;
        private FeaturePortShape featPortShape4;

        private TestConnector testCon;
        private TestConnector testCon2;
        private TestConnector testCon3;
        private TestConnector testCon4;

        private ConnectConnector featCon;
        private ConnectConnector featCon2;
        private ConnectConnector featCon3;

        public static ModelHelper GetModel(Boolean reset) {
            if (reset || instance == null) {
                instance = new ModelHelper();
            }
            return instance;
        }
        
        private ModelHelper() {
            modelElements = new Dictionary<String, Object>();
            store = new Store(typeof(AgileProductLinerDSLDomainModel));
            InitStore();
        }

        private void InitStore() {
            using (Transaction t = store.TransactionManager.BeginTransaction("Init model")) {
                InitModel();
                InitPresentation();
                //StoreViewer.Show(store);
                t.Commit();              
            }
        }

        private void InitModel(){

            Partition modelPart = store.Partitions.Values.First();
            
            featureModel = new FeatureModel(modelPart, new PropertyAssignment(FeatureModel.NameDomainPropertyId, "Feature Model"));
            ModelElements.Add("featureModel", featureModel);

            feature = new Feature(modelPart, new PropertyAssignment(Feature.NameDomainPropertyId, "Root feature"));
            feature2 = new Feature(modelPart, new PropertyAssignment(Feature.NameDomainPropertyId, "Feature 2"));
            feature3 = new Feature(modelPart, new PropertyAssignment(Feature.NameDomainPropertyId, "Feature 3"));
            feature4 = new Feature(modelPart, new PropertyAssignment(Feature.NameDomainPropertyId, "Feature 4"));
            ModelElements.Add("feature", feature);
            ModelElements.Add("feature2", feature2);
            ModelElements.Add("feature3", feature3);
            ModelElements.Add("feature4", feature4);


            rootFeatureLink = new FeatureModelHasFeatureModelElements(featureModel, feature);
            new FeatureModelHasFeatureModelElements(featureModel, feature2);
            new FeatureModelHasFeatureModelElements(featureModel, feature3);
            new FeatureModelHasFeatureModelElements(featureModel, feature4);

            FeatureModelElementReferencesSubFeatureModelElementsBuilder.Connect(feature, feature2);
            FeatureModelElementReferencesSubFeatureModelElementsBuilder.Connect(feature, feature3);
            FeatureModelElementReferencesSubFeatureModelElementsBuilder.Connect(feature, feature4);

            test = new Test(modelPart);
            test2 = new Test(modelPart);
            test3 = new Test(modelPart);
            ModelElements.Add("test", test);
            ModelElements.Add("test2", test2);
            ModelElements.Add("test3", test3);

            new FeatureModelHasTests(featureModel, test);
            new FeatureModelHasTests(featureModel, test2);
            new FeatureModelHasTests(featureModel, test3);
        
            TestConnectBuilder.Connect(feature2, test);
            TestConnectBuilder.Connect(feature2, test2);
            TestConnectBuilder.Connect(feature3, test2);
            TestConnectBuilder.Connect(feature4, test3);

            featPort = test.FeaturePorts[0];
            featPort2 = test2.FeaturePorts[0];
            featPort3 = test2.FeaturePorts[1];
            featPort4 = test3.FeaturePorts[0];
            ModelElements.Add("featurePort", featPort);
            ModelElements.Add("featurePort2", featPort2);
            ModelElements.Add("featurePort3", featPort3);
            ModelElements.Add("featurePort4", featPort4);

            testPort = feature2.TestPort;
            testPort2 = feature3.TestPort;
            testPort3 = feature4.TestPort;
            ModelElements.Add("testPort", testPort);
            ModelElements.Add("testPort2", testPort2);
            ModelElements.Add("testPort3", testPort3);
        }

        private void InitPresentation() {
            Partition presPart = new Partition(store);
            
            Diagram diagram = new AgileProductLinerDSLDiagram(presPart);

            PresentationViewsSubject.SetSubject(diagram,featureModel);
            
            featureShape = new FeatureShape(presPart);
            featureShape2 = new FeatureShape(presPart);
            featureShape3 = new FeatureShape(presPart);
            featureShape4 = new FeatureShape(presPart);
            ModelElements.Add("featureShape", featureShape);
            ModelElements.Add("featureShape2", featureShape2);
            ModelElements.Add("featureShape3", featureShape3);
            ModelElements.Add("featureShape4", featureShape4);

            PresentationViewsSubject.SetSubject(featureShape, feature);
            PresentationViewsSubject.SetSubject(featureShape2, feature2);
            PresentationViewsSubject.SetSubject(featureShape3, feature3);
            PresentationViewsSubject.SetSubject(featureShape4, feature4);

            testShape = new TestShape(presPart);
            testShape2 = new TestShape(presPart);
            testShape3 = new TestShape(presPart);
            ModelElements.Add("testShape", testShape);
            ModelElements.Add("testShape2", testShape2);
            ModelElements.Add("testShape3", testShape3);

            PresentationViewsSubject.SetSubject(testShape, test);
            PresentationViewsSubject.SetSubject(testShape2, test2);
            PresentationViewsSubject.SetSubject(testShape3, test3);

            testPortShape = new TestPortShape(presPart);
            testPortShape2 = new TestPortShape(presPart);
            testPortShape3 = new TestPortShape(presPart);
            ModelElements.Add("testPortShape", testPortShape);
            ModelElements.Add("testPortShape2", testPortShape2);
            ModelElements.Add("testPortShape3", testPortShape3);

            PresentationViewsSubject.SetSubject(testPortShape, feature2.TestPort);
            PresentationViewsSubject.SetSubject(testPortShape2, feature3.TestPort);
            PresentationViewsSubject.SetSubject(testPortShape3, feature4.TestPort);
            
            featPortShape = new FeaturePortShape(presPart);
            featPortShape2 = new FeaturePortShape(presPart);
            featPortShape3 = new FeaturePortShape(presPart);
            featPortShape4 = new FeaturePortShape(presPart);
            ModelElements.Add("featurePortShape", featPortShape);
            ModelElements.Add("featurePortShape2", featPortShape2);
            ModelElements.Add("featurePortShape3", featPortShape3);
            ModelElements.Add("featurePortShape4", featPortShape4);

            PresentationViewsSubject.SetSubject(featPortShape, featPort);
            PresentationViewsSubject.SetSubject(featPortShape2, featPort2);
            PresentationViewsSubject.SetSubject(featPortShape3, featPort3);
            PresentationViewsSubject.SetSubject(featPortShape4, featPort4);

            new ParentShapeContainsNestedChildShapes(diagram, featureShape);
            new ParentShapeContainsNestedChildShapes(diagram, featureShape2);
            new ParentShapeContainsNestedChildShapes(diagram, featureShape3);
            new ParentShapeContainsNestedChildShapes(diagram, featureShape4);
            new ParentShapeContainsNestedChildShapes(diagram, testShape);
            new ParentShapeContainsNestedChildShapes(diagram, testShape2);
            new ParentShapeContainsNestedChildShapes(diagram, testShape3);
            
            testCon = new TestConnector(presPart);
            testCon2 = new TestConnector(presPart);
            testCon3 = new TestConnector(presPart);
            testCon4 = new TestConnector(presPart);
            ModelElements.Add("testCon", testCon);
            ModelElements.Add("testCon2", testCon2);
            ModelElements.Add("testCon3", testCon3);
            ModelElements.Add("testCon4", testCon4);

            TestPortReferencesFeaturePorts testRefCon = TestPortReferencesFeaturePorts.GetLink(testPort, featPort);
            TestPortReferencesFeaturePorts testRefCon2 = TestPortReferencesFeaturePorts.GetLink(testPort, featPort2);
            TestPortReferencesFeaturePorts testRefCon3 = TestPortReferencesFeaturePorts.GetLink(testPort2, featPort3);
            TestPortReferencesFeaturePorts testRefCon4 = TestPortReferencesFeaturePorts.GetLink(testPort3, featPort4);
            ModelElements.Add("testConModel", testRefCon);
            ModelElements.Add("testConModel2", testRefCon2);
            ModelElements.Add("testConModel3", testRefCon3);
            ModelElements.Add("testConModel4", testRefCon4);

            PresentationViewsSubject.SetSubject(testCon, testRefCon);
            PresentationViewsSubject.SetSubject(testCon2, testRefCon2);
            PresentationViewsSubject.SetSubject(testCon3, testRefCon3);
            PresentationViewsSubject.SetSubject(testCon4, testRefCon4);

            featCon = new ConnectConnector(presPart);
            featCon2 = new ConnectConnector(presPart);
            featCon3 = new ConnectConnector(presPart);
            ModelElements.Add("featCon", featCon);
            ModelElements.Add("featCon2", featCon2);
            ModelElements.Add("featCon3", featCon3);

            link = FeatureModelElementReferencesSubFeatureModelElements.GetLink(feature, feature2);
            link2 = FeatureModelElementReferencesSubFeatureModelElements.GetLink(feature, feature3);
            link3 = FeatureModelElementReferencesSubFeatureModelElements.GetLink(feature, feature4);

            PresentationViewsSubject.SetSubject(featCon, link);
            PresentationViewsSubject.SetSubject(featCon2, link2);
            PresentationViewsSubject.SetSubject(featCon3, link3);

            
            new ParentShapeContainsNestedChildShapes(diagram, testCon);
            new ParentShapeContainsNestedChildShapes(diagram, testCon2);
            new ParentShapeContainsNestedChildShapes(diagram, testCon3);
            new ParentShapeContainsNestedChildShapes(diagram, testCon4);

            new ParentShapeHasRelativeChildShapes(featureShape2, testPortShape);
            new ParentShapeHasRelativeChildShapes(featureShape3, testPortShape2);
            new ParentShapeHasRelativeChildShapes(featureShape4, testPortShape3);
            new ParentShapeHasRelativeChildShapes(testShape, featPortShape);
            new ParentShapeHasRelativeChildShapes(testShape2, featPortShape2);
            new ParentShapeHasRelativeChildShapes(testShape2, featPortShape3);
            new ParentShapeHasRelativeChildShapes(testShape3, featPortShape4);

            new LinkConnectsToNode(testCon, testPortShape);
            new LinkConnectsToNode(testCon, featPortShape);

            new LinkConnectsToNode(testCon2, testPortShape);
            new LinkConnectsToNode(testCon2, featPortShape2);

            new LinkConnectsToNode(testCon3, testPortShape2);
            new LinkConnectsToNode(testCon3, featPortShape3);

            new LinkConnectsToNode(testCon4, testPortShape3);
            new LinkConnectsToNode(testCon4, featPortShape4);
        }

        private IDictionary<String, Object> ModelElements {
            get {
                return this.modelElements;
            }
            set {
                this.modelElements = value;
            }
        }

        public Store Store {
            get {
                return this.store;
            }
            set {
                this.store = value;
            }
        }

        /// <summary>
        /// Everything that can be found in the Dictionary has to be added in the InitXXX() methods of the model helper
        /// class.
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="elementName"></param>
        /// <returns>Found element</returns>
        public T GetElement<T>(String elementName) {
            if (!this.ModelElements.ContainsKey(elementName)) return default(T);
            Object element = this.ModelElements[elementName];
            if (element == null) return default(T);
            if (element is T) return (T)element;
            else return default(T);
        }
    }
}
